STM32 |
您所在的位置:网站首页 › 时钟 单片机 › STM32 |
前言 时钟系统是MCU必不可少的一部分,本文将针对以下问题进行讨论: 什么是时钟?如何理解STM32时钟结构?为什么MCU会有多个时钟源?STM32如何配置时钟 ?目录 一、时钟简介 二、STM32时钟树 2.1、STM32F10X时钟源 2.2、系统时钟 三、关于时钟的一些思考 3.1、 时钟频率 3.2、多个时钟源 四、时钟配置分析 1、开始HSE时钟 2、等待HSE就绪(因为时钟起振等原因一定要有一部分时间缓冲稳定) 3、HSE状态正常就继续,异常就立即处理 3.1.1 HSE状态正常情况分析 3.1.2 设置AHB 、APB1、APB2的预分频因子 3.1.3 选择PLL的时钟源 3.1.4 开启PLL时钟并等待就绪(凡是开启了就要等待) 3.1.5 选择PLL作为系统时钟源(默认也是这样) 五、总结 5.1 配置时钟的流程 5.2 学习一款MCU的时钟流程 一、时钟简介首先,我们得搞清楚几个概念: 时钟:单片机的心脏,所有的外设的运作都需要时钟供能。时钟周期:又称为振荡周期,可以简单理解为传输一个0或1所需要的时间指令周期:执行一条指令(如 MOV A, #34H)所需要的时间。对于不同类型的指令,指令周期长度可能不同。机器周期:执行一个动作的时间周期。如:执行一个指令需要”取指令并译码“、”执行操作数“两个动作。以上三个周期的关系图如下:(图为转载) (其中蓝色部分包括了四个机器周期) 而因为时钟信号也是电信号的一种,所以它还兼具了供能的作用,且因电平变化的时间间隔一定,我们甚至可以用时钟来计时间(PS:这里时钟”供能“的说法是错误的,时钟并不能起到供能的作用,但是我觉得对于初学者而言,这样感性的理解问题也不大,只有记得时钟并不供能即可) 二、STM32时钟树有了以上的感观认识之后,我们先来看看STM32F10X的时钟树: 上图蓝色部分标出了 STM32的5个时钟源,分别是: HSI:高速内部时钟(High Speed Internal),由芯片内部RC振荡器产生,频率为8MHZHSE:高速外部时钟(High Speed External),可外接4-16MHZ的晶振作为时钟源LSE:低速外部时钟,外接32.768KHZ时钟源LSI:低速内部时钟,由芯片内部的RC振荡器产生,频率为40KHZPLL:锁相环倍频输出。可以做到输入时钟的2-16倍的倍频输出PS:这里把PLL也列为时钟源是参考了诸如正点原子和野火资料的说法,个人觉得实际的时钟源严格来说只能算4个,因为PLL本身只起倍频作用,本身并不能产生时钟频率。当然,这些都是人为定义的,不会影响使用和该知识点的理解,且有助于理解该知识。 2.2、系统时钟知道有哪些时钟源之后,我们可以发现时钟树其实是我们熟知的AHB、APB1、APB2总线 时钟源与这些外设之间都通过红色部分的系统时钟SYSCLK连接在一起,由此观之这个系统时钟SYSCLK很重要!!! 可以看到,系统时钟有三个输入源: HSI:8MHZPLLCLK:8-128MHZ(外接晶振一般为8MHZ)HSE:4-16MHZ而SYSCLK可以接受的最大频率是72MHZ,这也是后面所有外设可以正常运作的频率。 可以见到,想要达到这个数字,只能通过PLL倍频得到(不止得到,你还可以用它超频) 对了,SYSCLK选择还连着一个CSS的东西,查了数据手册可以知道,CSS(时钟安全系统) 可以看到,这东西简单点来说就是一个报警器。帮你检测HSE有没有问题,有的话先切换到内部时钟源HSI,然后提示相关信息。 分析到这里,我们对STM32时钟树可以有个相对清晰的认识:时钟源、然后配置总线时钟(AHB、APB1、APB2),最后具体总线上的外设的时钟挂载在总线时钟上 下面补充下没有提到的点: 低速内外部时钟应用(LSI和LSE)低速时钟LSI和LSE简单从时钟树图中也可以看到,其中这两者都可以作为RTC(实时时钟)的时钟源,并且HSE的分频也可以,再者LSI可以作为独立看门狗时钟 MCO时钟输出:用于示波器观察时钟信号,调试用,简单来说可以测时钟源的频率,直观看波形 三、关于时钟的一些思考 3.1、 时钟频率从2.1和2.2的讨论中,我们可以发现,系统时钟正常工作的时钟频率是72MHZ。但实际输入的系统时钟频率最大可以达到128MHZ,最低8MHZ,这意味着我们可以不遵循数据手册的规定,做点"违规操作" 我从网上找的的回答有: 频率高了,系统稳定性会受到影响,具体表现是进行通信的时候,外设可能无法接收到准确的信号以及功耗会变大频率低了,不能满足设备的频率要求,可能无法启动设备 3.2、多个时钟源可能大家一开始接触时钟都会有这样一个疑问,为什么需要那么多时钟源,又分内部时钟源和外部时钟源?尽然外设都是受系统时钟管理,那么只用一个时钟不是就足够了吗? 基于以上的问题,翻阅资料和网上回答,得到的两个明显的结论: 多个时钟源可以在单个时钟源发生故障时,起到救急的作用一个外设有多个时钟源,可以根据需要选择相应频率的时钟源 四、时钟配置分析STM32时钟树理解之后,就是时钟的配置问题了。 实际上,当我们创建工程导入启动文件的时候,在main函数开始调用之前,启动文件就已经调用了SystemInit函数对系统时钟进行初始化了。(这里说下启动文件的5个作用,设置堆栈指针、设置PC指针、初始化中断向量表、配置系统时钟、调用C库函数_main最终去到C的世界)。 那为什么还需要做配置分析?直接拿来用不就好了嘛? 我们且不论其它芯片如何,单就看stm32的固件库给我们提供的库函数(下图system_stm32f10x.c的截图):
人家ST官方只提供了24MHz,36MHz,48MHz,56MHz,72MHz五种频率的系统时钟设置。 如果项目对时钟有这5种频率外的需求还是得自己动手。基于以上原因,对时钟配置的分析就是刚需了。 那么,先让我们对着时钟树的图再梳理一遍系统时钟的来龙去脉。 输入源: HSI直接输出,频率只有8MHz。HSE直接输出,范围4-16MHz,一般为8MHz。PLL:锁相环倍频输出,倍频范围是2-16倍。且PLL自身有两个输入源:HSI 二分频输出至PLL,这种方式的系统时钟最高可至64MHz。HSE直接输出至PLL。输出源: 直接输出。输出至AHB总线,经预分频器可做 1~512 分频。AHB总线预分频后,经低速外设总线APB1和高速总线APB2。因为考虑到功耗的问题,外设只有用到的时候才会开启时钟,所以SYSCLK只需要初始化总线时钟即可。 基于这个原因,对于系统时钟配置的流程大概可以总结为以下步骤: 1、选择并开启时钟源(使用PLL作为系统时钟源还需要选择倍频系数) 2、选择APB1、APB2和AHB的预分频系数 我想这里肯定有人和我一样,初学的时候有这样的疑惑,你这怎么一下子从时钟树的左边一下子跳到最右边,简直毫无逻辑可言(cnm),其实不然 如果AHB先开启,而APB1和APB2未设置的话会出现紊乱 下面以库函数 SetSysClockTo72()为示例,搭配数据手册学习配置过程,先把源码贴上: * @brief Sets System clock frequency to 72MHz and configure HCLK, PCLK2 * and PCLK1 prescalers. * @note This function should be used only after reset. * @param None * @retval None */ static void SetSysClockTo72(void) { __IO uint32_t StartUpCounter = 0, HSEStatus = 0; /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/ /* Enable HSE */ RCC->CR |= ((uint32_t)RCC_CR_HSEON); /* Wait till HSE is ready and if Time out is reached exit */ do { HSEStatus = RCC->CR & RCC_CR_HSERDY; StartUpCounter++; } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT)); if ((RCC->CR & RCC_CR_HSERDY) != RESET) { HSEStatus = (uint32_t)0x01; } else { HSEStatus = (uint32_t)0x00; } if (HSEStatus == (uint32_t)0x01) { /* Enable Prefetch Buffer */ FLASH->ACR |= FLASH_ACR_PRFTBE; /* Flash 2 wait state */ FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY); FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2; /* HCLK = SYSCLK */ RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1; /* PCLK2 = HCLK */ RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; /* PCLK1 = HCLK */ RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; #ifdef STM32F10X_CL /* Configure PLLs ------------------------------------------------------*/ /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */ /* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */ RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL | RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC); RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 | RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5); /* Enable PLL2 */ RCC->CR |= RCC_CR_PLL2ON; /* Wait till PLL2 is ready */ while((RCC->CR & RCC_CR_PLL2RDY) == 0) { } /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL); RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | RCC_CFGR_PLLMULL9); #else /* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL)); RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9); #endif /* STM32F10X_CL */ /* Enable PLL */ RCC->CR |= RCC_CR_PLLON; /* Wait till PLL is ready */ while((RCC->CR & RCC_CR_PLLRDY) == 0) { } /* Select PLL as system clock source */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL; /* Wait till PLL is used as system clock source */ while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { } } else { /* If HSE fails to start-up, the application will have wrong clock configuration. User can add here some code to deal with this error */ } } #endif第一眼看上去很长,寄存器也很多,但是没有关系。先把出现的寄存器记下来: RCC->CRRCC->CFGRRCC->CFGR2FLASH->ACR然后干嘛?当然是去查查手册这些寄存器是干嘛用的呀!由于还是小萌新阶段,只需要了解上面每个寄存器大概的作用结合源码学习即可,手册暂时不必深究。
结合以上认识,下面我们来一段一段分析源码:(耐心对着参考手册看寄存器的各个位的作用) 1、开始HSE时钟 * SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/ /* Enable HSE */ RCC->CR |= ((uint32_t)RCC_CR_HSEON);//HSEON -> HSE_ON 2、等待HSE就绪(因为时钟起振等原因一定要有一部分时间缓冲稳定) /* Wait till HSE is ready and if Time out is reached exit */ do { HSEStatus = RCC->CR & RCC_CR_HSERDY; StartUpCounter++; } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT)); if ((RCC->CR & RCC_CR_HSERDY) != RESET) { HSEStatus = (uint32_t)0x01; } else { HSEStatus = (uint32_t)0x00; } 3、HSE状态正常就继续,异常就立即处理状态异常的话,经条件语句跳到下面用户可以自行定义异常处理 这里涉及到FLASH编程的知识,其中我看正点原子给的解释是CPU的反应速度快于FLASH的速度,所以这里需要等待两个状态,等待几个状态看你的时钟配置,可以看FLASH编程手册,里面有讲解(48-72MHZ是两个状态时间) 3.1.2 设置AHB 、APB1、APB2的预分频因子 /* HCLK = SYSCLK */ RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1; /* PCLK2 = HCLK */ RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; /* PCLK1 = HCLK */ RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; 3.1.3 选择PLL的时钟源源码里这段其实还有二三十行代码,不过那是互联型芯片的配置代码,这里f103是基础型,所以先暂时略过不看。 /* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL)); /* HSE 与上 PLLMULL9 做到 72MHz的系统时钟输出 */ RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9); 3.1.4 开启PLL时钟并等待就绪(凡是开启了就要等待) /* Enable PLL */ RCC->CR |= RCC_CR_PLLON; /* Wait till PLL is ready */ while((RCC->CR & RCC_CR_PLLRDY) == 0) { } 3.1.5 选择PLL作为系统时钟源(默认也是这样) /* Select PLL as system clock source */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL; /* Wait till PLL is used as system clock source */ while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { } 五、总结 5.1 配置时钟的流程 开启时钟并等待就绪若时钟异常就退出(用户可自行作相关异常处理)开启正常,先配置相关总线的预分频系数最后选择系统时钟是PLL还是HSI或者HSE,让外设得到时钟 5.2 学习一款MCU的时钟流程 看时钟树——分析时钟的来龙去脉查数据手册,找到以下问题的答案:时钟源怎么开启?相关外设的时钟怎么控制系统时钟源怎么选择最后,理论赋予实践,自己动手配置一个其他的时钟或者自己写一个时钟配置函数,下面可参考我写的基于固件库函数的时钟配置函数写法(ST官方的都是基于寄存器编程的配置) |
今日新闻 |
点击排行 |
|
推荐新闻 |
图片新闻 |
|
专题文章 |
CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭 |